<?php
// +----------------------------------------------------------------------
// | Name 风音框架
// +----------------------------------------------------------------------
// | Author 唐启云 <tqy@fxri.net>
// +----------------------------------------------------------------------
// | Copyright Copyright © 2016-2099 方弦研究所. All rights reserved.
// +----------------------------------------------------------------------
// | Link https://www.fxri.net
// +----------------------------------------------------------------------
namespace fxapp\facade_ext;

/**
 * 精灵类
 */
class Elven
{
    /**
     * 处理接口-校验
     * @param mixed $var 变量
     * @param string $type 类型
     * @return mixed
     */
    public function apiCheck($var, $type)
    {
        // 初始化变量
        $api_wrap = $this->apiWrapper('response');
        $type = strtolower($type);
        $echo[0] = true;
        $echo[1] = $type;
        $echo[2] = null;
        // 参数格式检测
        if (!is_json($var) && !is_array($var)) {
            $api_wrap['api'] = 'weic.envoy.app.magic.message';
            $api_wrap['data']['state'] = 1001;
            $api_wrap['data']['message'] = \fxapp\Base::lang(['api', 'format', 'error']);
            $api_wrap = \fxapp\Param::json($api_wrap, 'encode');
            $echo[0] = false;
            $echo[1] = 'response';
            $echo[2] = $api_wrap;
            return $echo;
        } else if (is_json($var)) {
            $var = \fxapp\Param::json($var, 'decode');
        }
        // 参数非空检测
        $predefined = [
            'api', 'data', 'id', 'version',
        ];
        $var = \fxapp\Param::define([$var, $predefined], '1.2.1');
        $parm['api'] = $var['api'];
        $parm['data'] = $var['data'];
        $parm['version'] = $var['version'];
        $tray['check'] = \fxapp\Data::paramEmpty($parm);
        $parm['id'] = $var['id'];
        if (!$tray['check'][0]) {
            $api_wrap['api'] = $parm['api'];
            $api_wrap['data'] = $this->dataFormat($tray['check'], 1);
            $api_wrap['id'] = $parm['id'];
            $api_wrap['version'] = $parm['version'];
            $api_wrap = \fxapp\Param::json($api_wrap, 'encode');
            $echo[0] = false;
            $echo[1] = 'response';
            $echo[2] = $api_wrap;
            return $echo;
        }
        // 参数格式检测
        if (!is_array($var['data'])) {
            $api_wrap['api'] = $parm['api'];
            $api_wrap['data']['state'] = 1001;
            $api_wrap['data']['message'] = \fxapp\Base::lang(['api', 'format', 'error']);
            $api_wrap['id'] = $parm['id'];
            $api_wrap['version'] = $parm['version'];
            $api_wrap = \fxapp\Param::json($api_wrap, 'encode');
            $echo[0] = false;
            $echo[1] = 'response';
            $echo[2] = $api_wrap;
            return $echo;
        }
        switch ($parm['version']) {
            default:
                // 未知
                $api_wrap['api'] = $parm['api'];
                $api_wrap['data']['state'] = 1001;
                $api_wrap['data']['message'] = \fxapp\Base::lang(['api', 'version', 'error']);
                $api_wrap['id'] = $parm['id'];
                $api_wrap['version'] = $parm['version'];
                $api_wrap = \fxapp\Param::json($api_wrap, 'encode');
                $echo[0] = false;
                $echo[1] = 'response';
                $echo[2] = $api_wrap;
                return $echo;
                break;
            case '1.0':
                // 版本-V1.0
            case '1.1':
                // 版本-V1.1
                switch ($type) {
                    case 'request':
                        // 请求
                        $predefined = [
                            'core' => [], 'auth' => [], 'data' => [],
                        ];
                        $var['data'] = \fxapp\Param::define([$var['data'], $predefined], '1.1.1');
                        $var['data']['core'] = \fxapp\Base::json($var['data']['core'], 'decode');
                        $var['data']['auth'] = \fxapp\Base::json($var['data']['auth'], 'decode');
                        $var['data']['data'] = \fxapp\Base::json($var['data']['data'], 'decode');
                        $api_wrap = \fxapp\Param::json($var, 'encode');
                        break;
                    case 'response':
                        // 响应
                        $predefined = [
                            'state' => -1, 'message' => '', 'data' => [],
                        ];
                        $var['data'] = \fxapp\Param::define([$var['data'], $predefined], '1.1.1');
                        $var['data']['data'] = \fxapp\Base::json($var['data']['data'], 'decode');
                        $api_wrap = \fxapp\Param::json($var, 'encode');
                        break;
                }
                break;
            case '2.0':
                // 版本-V2.0
                switch ($type) {
                    case 'request':
                        // 请求
                        $predefined = [
                            'core' => [], 'data' => [],
                        ];
                        $var['data'] = \fxapp\Param::define([$var['data'], $predefined], '1.1.1');
                        $var['data']['core'] = \fxapp\Base::json($var['data']['core'], 'decode');
                        $var['data']['data'] = \fxapp\Base::json($var['data']['data'], 'decode');
                        $api_wrap = \fxapp\Param::json($var, 'encode');
                        break;
                    case 'response':
                        // 响应
                        $predefined = [
                            'state' => -1, 'message' => '', 'data' => [],
                        ];
                        $var['data'] = \fxapp\Param::define([$var['data'], $predefined], '1.1.1');
                        $var['data']['data'] = \fxapp\Base::json($var['data']['data'], 'decode');
                        $api_wrap = \fxapp\Param::json($var, 'encode');
                        break;
                }
                break;
        }
        $echo[2] = $api_wrap;
        return $echo;
    }

    /**
     * 处理接口-包装
     * @param integer $type 类型
     * @return array
     */
    public function apiWrapper($type)
    {
        $data = null;
        switch ($type) {
            case 'request':
                // 请求接口
                $data = [
                    // 接口
                    'api' => '',
                    // 数据
                    'data' => [
                        // 核心数据
                        'core' => [],
                        // 参数数据
                        'data' => [],
                    ],
                    // 编号
                    'id' => '',
                    // 版本
                    'version' => '2.0',
                ];
                break;
            case 'response':
                // 响应接口
                $data = [
                    // 接口
                    'api' => '',
                    // 数据
                    'data' => [
                        // 状态代码
                        'state' => 200,
                        // 提示信息
                        'message' => '',
                        // 参数数据
                        'data' => [],
                    ],
                    // 编号
                    'id' => '',
                    // 版本
                    'version' => '2.0',
                ];
                break;
        }
        return $data;
    }

    /**
     * 调试
     * @param mixed $param 参数
     * @param mixed $level 等级
     * @return string
     */
    public function dump($param, $level)
    {
        // 初始化变量
        static $cace, $debug, $log;
        $cace['name'] = [
            // 错误
            1 => 'ERROR',
            // 警告
            2 => 'WARN',
            // 信息
            3 => 'INFO',
            // 调试
            4 => 'DEBUG',
            // 跟踪
            5 => 'TRACE',
            // 仓库
            6 => 'DEPOT',
            // 忽视
            7 => 'IGNORE',
        ];
        // 配置参数
        $config = \fxapp\Base::config('elven.common');
        if (is_null($debug) || is_null($log)) {
            // 解析调试等级
            $debug['switch'] = $config['debug'];
            $debug['level'] = array_flip(\fxapp\Text::explode(',', $config['debug_level']));
            // 解析日志等级
            $log['switch'] = $config['log'];
            $log['level'] = array_flip(\fxapp\Text::explode(',', $config['log_level']));
        }
        if (is_string($param) || is_numeric($param)) {
            $data = [\fxapp\Text::timeMilli(\fxapp\Text::timeMilli()), ' ', $cace['name'][$level], ' LEN:', strlen($param), ' ', $param, PHP_EOL];
        } else {
            $data = [\fxapp\Text::timeMilli(\fxapp\Text::timeMilli()), ' ', $cace['name'][$level], ' LEN:', strlen(var_dump($param)), PHP_EOL, var_dump($param)];
        }
        $data = implode('', $data);
        if (!$debug['switch']) return;
        if (isset($debug['level'][$level]) && !$config['daemon']) {
            echo $data;
        }
        if ($log['switch'] && isset($log['level'][$level]) && is_file($config['log_file']['routine'])) {
            file_put_contents($config['log_file']['routine'], $data, FILE_APPEND);
        }
    }

    /**
     * 输出
     * @param mixed $string 字符串
     * @param boolean $stamp 标记
     * @return mixed
     */
    public function echo($string, $stamp = false)
    {
        // 初始化变量
        $data = [];
        // 配置参数
        $config = \fxapp\Base::config('elven.common');
        // 配置数据
        if (!is_string($string) && !is_array($string)) {
            return;
        } else if (is_array($string)) {
            $string = implode('', $string);
        }
        if ($stamp) {
            $data[] = \fxapp\Text::timeMilli(\fxapp\Text::timeMilli()) . ' ';
        }
        $data[] = $string;
        $data[] = PHP_EOL;
        $data = implode('', $data);
        if (!$config['log']) return;
        if (!$config['daemon']) {
            echo $data;
        }
        if (is_file($config['log_file']['routine'])) {
            file_put_contents($config['log_file']['routine'], $data, FILE_APPEND);
        }
    }

    /**
     * 异常
     * @param mixed $param 参数
     * @param mixed $level 等级
     * @return string
     */
    public function exception($param, $level)
    {
        // 初始化变量
        static $cace, $log;
        $cace['name'] = [
            // 错误
            1 => 'ERROR',
            // 警告
            2 => 'WARN',
            // 信息
            3 => 'INFO',
            // 调试
            4 => 'DEBUG',
            // 跟踪
            5 => 'TRACE',
            // 仓库
            6 => 'DEPOT',
            // 忽视
            7 => 'IGNORE',
        ];
        // 配置参数
        $config = \fxapp\Base::config('elven.common');
        if (is_null($log)) {
            // 解析日志等级
            $log['switch'] = $config['log'];
            $log['level'] = array_flip(\fxapp\Text::explode(',', $config['log_level']));
        }
        if (is_string($param) || is_numeric($param)) {
            $data = [\fxapp\Text::timeMilli(\fxapp\Text::timeMilli()), ' ', $cace['name'][$level], ' LEN:', strlen($param), ' ', $param, PHP_EOL];
        } else {
            $data = [\fxapp\Text::timeMilli(\fxapp\Text::timeMilli()), ' ', $cace['name'][$level], ' LEN:', strlen(var_dump($param)), PHP_EOL, var_dump($param)];
        }
        $data = implode('', $data);
        if ($log['switch'] && isset($log['level'][$level]) && is_file($config['log_file']['error'])) {
            file_put_contents($config['log_file']['error'], $data, FILE_APPEND);
        }
    }

    /**
     * 处理数据-解析
     * @param string $data 数据
     * @return string
     */
    public function dataParse($data)
    {
        // 字符串转16进制
        if (is_string($data) && !is_json($data) && false == \fxapp\Text::convertExt($data, 'hex3')) {
            return 'PARSE ' . \fxapp\Text::convertExt($data, 'hex2');
        }
        return $data;
    }

    /**
     * 参数
     * @param array $param 参数
     * @param string $conf 配置
     * @return mixed
     */
    public function param($param, $conf)
    {
        // 初始化变量
        global $argv;
        // 获取参数配置
        $common = \fxapp\Base::config('elven.common');
        $common['name'] = $conf;
        $config = \fxapp\Base::config($common['name']);
        // 参数非空设置
        $predefined = [
            1, 2, 3,
        ];
        $argv = \fxapp\Param::define([$argv, $predefined], '1.2.2');
        // 动态配置-快捷配置
        $predefined = [
            'command' => $argv[2],
        ];
        $param = \fxapp\Param::define([$param, $predefined], '1.1.2');
        $parm = \fxapp\Text::explode('-', $argv[3]);
        foreach ($parm as $value) {
            switch ($value) {
                case 'd':
                    // 守护进程
                case 'daemon':
                    $param['daemon'] = true;
                    break;
                case 'debug':
                    // 调试模式
                    $param['debug'] = true;
                    break;
                case 'l':
                case 'log':
                    // 日志模式
                    $param['log'] = true;
                    break;
                case 'dynamic':
                    // 动态模式
                    $param['dynamic'] = true;
                    break;
            }
        }
        // 动态配置-请求参数
        $predefined = [
            'daemon', 'debug', 'debug_level',
            'log', 'log_level', 'log_dir',
            'log_file', 'dynamic',
        ];
        $param = \fxapp\Param::define([$param, $predefined], '1.2.2');
        \fxapp\Base::config('elven.common', array_merge($common, $config));
        return $param;
    }

    /**
     * UDP通信
     * @param string $ip IP地址
     * @param string $port 端口
     * @param string $message 信息
     * @param string $type 类型
     * @return mixed
     */
    public function udpSend($ip = '', $port = '', $message = '', $type = null)
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            // 使用IPV4格式地址，数据报形式，UDP方式传输数据
            $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        } else if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            // 使用IPV6格式地址，数据报形式，UDP方式传输数据
            $socket = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP);
        } else {
            return;
        }
        switch ($type) {
            default:
            case 1:
                // 普通模式
                break;
            case 2:
                // 广播模式
                socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
                break;
        }
        // 发送信息
        $message = str_split($message, 65507);
        foreach ($message as $value) {
            socket_sendto($socket, $value, strlen($value), 0, $ip, $port);
        }
        // 关闭连接
        socket_close($socket);
    }

    /**
     * 转码
     * @param mixed $data 数据
     * @return mixed
     */
    public function transcode($data)
    {
        if (is_array($data) || is_object($data)) {
            $data = \fxapp\Param::json($data, 'encode');
        }
        $echo = $data;
        if (is_string($echo) && strpos($echo, '0x') === 0) {
            $echo = substr($echo, 2);
            $echo = str_replace(' ', '', $echo);
            $echo = \fxapp\Text::convert($echo, 'strhex');
            if (false === $echo) $echo = $data;
        }
        return $echo;
    }

    /**
     * 工作类
     * @param string $name 名称
     * @param string $type 类型
     * @return mixed
     */
    public function worker($name, $type)
    {
        // 初始化变量
        try {
            if (!is_string($name) || !is_string($type)) return;
            // 处理名称
            $name = strtolower($name);
            $name = explode('_', $name);
            foreach ($name as $key => $value) {
                $name[$key] = ucfirst($value);
            }
            $name = implode('', $name);
            // 处理类型
            $type = strtolower($type);
            $class = '\\app\\elven\\worker\\' . $type . '\\' . $name;
            // 处理工作类
            return new $class();
        } catch (\Throwable $th) {
            $dump_prefix = \fxapp\Base::lang(['source', '->', '精灵-工作类']) . ' | ';
            $this->dump($dump_prefix . \fxapp\Text::throwable($th), 1);
            $this->exception($dump_prefix . $th, 1);
        }
    }

    /**
     * 处理数据-格式化
     * @param array $data 数据
     * @param string $type 类型
     * @return mixed
     */
    public function dataFormat($data, $type)
    {
        $inform = [];
        switch ($type) {
            default:
                $inform = $data;
                break;
            case 1:
                // 输入参数处理
                $predefined = [
                    false, ['system', 'busy'], ['data' => []],
                    -1,
                ];
                $data = \fxapp\Param::define([$data, $predefined], '1.1.1');
                // 消息处理
                if (\fxapp\Data::paramEmpty([$data[1]])[0]) {
                    $data[2] = \fxapp\Base::lang($data[1]);
                }
                // 结果处理
                $inform = ['state' => $data[3], 'message' => $data[1]];
                if (is_array($data[2]) && \fxapp\Data::paramEmpty($data[2], false)[0]) {
                    foreach ($data[2] as $key => $value) {
                        $inform[$key] = $value;
                    }
                }
                // 配置扩展数据
                if (isset($inform['extend'])) {
                    $inform['extend'] = \fxapp\Param::object($inform['extend']);
                }
                break;
        }
        return $inform;
    }

    /**
     * 处理数据-封包
     * @param mixed $var 变量
     * @param string $type 类型
     * @param string $param 参数
     * @return mixed
     */
    public function dataPack($var, $type, $param = null)
    {
        // 初始化变量
        $echo[0] = $var;
        $echo[1] = null;
        $echo[2] = null;
        $echo[3] = null;
        $type = strtolower($type);
        switch ($param) {
            default:
                // 原文
                $echo[1] = true;
                $echo[2] = $var;
                return $echo;
            case 1:
                // 解析报文
                switch ($type) {
                        // 编码
                    case 'encode':
                        if (!is_string($var) && !is_numeric($var)) {
                            // 非字符串或数字
                            return $echo;
                        }
                        $rand = \fxapp\Math::rand(4);
                        $data = $rand . str_pad(strlen($var), 8, 0, STR_PAD_LEFT) . $var . $rand;
                        $echo[1] = true;
                        $echo[2] = $data;
                        return $echo;
                        // 解码
                    case 'decode':
                        if (!is_string($var)) {
                            // 非字符串
                            return $echo;
                        } else if (strlen($var) <= 16) {
                            // 字符串长度低于最低解析长度，等待继续解析
                            $var = trim($var);
                            $echo[1] = false;
                            $echo[3] = $var;
                            return $echo;
                        }
                        $head = substr($var, 0, 4);
                        $length = substr($var, 4, 8);
                        if (!is_numeric($length)) {
                            // 长度非数字
                            return $echo;
                        } else if (strlen($var) < 16 + $length) {
                            // 字符串长度低于最低解析长度，等待继续解析
                            $echo[1] = false;
                            $echo[3] = $var;
                            return $echo;
                        }
                        $body = substr($var, 12, $length);
                        $tail = substr($var, 12 + $length, 4);
                        $over = substr($var, 16 + $length);
                        if ($head != $tail) {
                            // 包头包尾不一致，丢弃此包，等待继续解析
                            $echo[1] = false;
                            $echo[3] = $over;
                            return $echo;
                        }
                        $echo[1] = true;
                        $echo[2] = $body;
                        $echo[3] = $over;
                        return $echo;
                }
                break;
            case 2:
                // 解析报文-UTF-8
                switch ($type) {
                        // 编码
                    case 'encode':
                        if (!is_string($var) && !is_numeric($var)) {
                            // 非字符串或数字
                            return $echo;
                        }
                        $rand = \fxapp\Math::rand(4);
                        $data = $rand . str_pad(mb_strlen($var, 'utf-8'), 8, 0, STR_PAD_LEFT) . $var . $rand;
                        $echo[1] = true;
                        $echo[2] = $data;
                        return $echo;
                        // 解码
                    case 'decode':
                        if (!is_string($var)) {
                            // 非字符串
                            return $echo;
                        } else if (mb_strlen($var, 'utf-8') <= 16) {
                            // 字符串长度低于最低解析长度，等待继续解析
                            $var = trim($var);
                            $echo[1] = false;
                            $echo[3] = $var;
                            return $echo;
                        }
                        $head = mb_substr($var, 0, 4, 'utf-8');
                        $length = mb_substr($var, 4, 8, 'utf-8');
                        if (!is_numeric($length)) {
                            // 长度非数字
                            return $echo;
                        } else if (mb_strlen($var, 'utf-8') < 16 + $length) {
                            // 字符串长度低于最低解析长度，等待继续解析
                            $echo[1] = false;
                            $echo[3] = $var;
                            return $echo;
                        }
                        $body = mb_substr($var, 12, $length, 'utf-8');
                        $tail = mb_substr($var, 12 + $length, 4, 'utf-8');
                        $over = mb_substr($var, 16 + $length, null, 'utf-8');
                        if ($head != $tail) {
                            // 包头包尾不一致，丢弃此包，等待继续解析
                            $echo[1] = false;
                            $echo[3] = $over;
                            return $echo;
                        }
                        $echo[1] = true;
                        $echo[2] = $body;
                        $echo[3] = $over;
                        return $echo;
                }
                break;
        }
        return $echo;
    }

    /**
     * 处理数据-加密
     * @param mixed $var 变量
     * @param string $type 类型
     * @param string $param 参数
     * @return mixed
     */
    public function dataCrypt($var, $type, $param = null)
    {
        // 初始化变量
        $data = null;
        $type = strtolower($type);
        switch ($param) {
            default:
                // 原文
                $data = $var;
                return $data;
            case 1:
                // 解析报文-Base64
                switch ($type) {
                        // 编码
                    case 'encode':
                        $data = base64_encode($var);
                        return $data;
                        // 解码
                    case 'decode':
                        $data = base64_decode($var);
                        return $data;
                }
                break;
            case 2:
                // 解析报文-DES
                switch ($type) {
                    case 'encode':
                        // 编码
                        $data = $var;
                        return $data;
                    case 'decode':
                        // 解码
                        $data = $var;
                        return $data;
                }
                break;
            case 3:
                // 解析报文-RSA
                switch ($type) {
                    case 'encode':
                        // 编码
                        if (!is_string($var)) {
                            // 非字符串
                            return false;
                        }
                        $var = \fxapp\Safe::rsapriExt($var, 'encode');
                        $var = base64_encode($var);
                        $data = $var;
                        return $data;
                    case 'decode':
                        // 解码
                        if (!is_string($var)) {
                            // 非字符串
                            return false;
                        }
                        $var = base64_decode($var);
                        $var = \fxapp\Safe::rsapriExt($var, 'decode');
                        $data = $var;
                        return $data;
                }
                break;
        }
        return $data;
    }

    /**
     * 处理数据-校验
     * @param array|string $clerk 主簿
     * @param array|string $data 数据
     * @return mixed
     */
    public function dataCheck($clerk, $data)
    {
        global $config;
        // 基础检查
        if (!is_json($clerk) && !is_array($clerk)) {
            return;
        } else if (is_json($clerk)) {
            $clerk = \fxapp\Param::json($clerk, 'decode');
        }
        // 数据检查
        if (!is_string($data) && !is_array($data)) {
            return;
        } else if (is_array($data)) {
            $data = \fxapp\Param::json($data, 'encode');
        }
        switch ($clerk['type']) {
            case 'term':
                // 使节服务
                if (!isset($clerk['work']) || $clerk['work'] != 'mqtt') break;
                $predefined = [
                    'reply', 'topic',
                ];
                $clerk = \fxapp\Param::define([$clerk, $predefined], '1.2.2');
                if (!$clerk['reply']) break;
                $response['topic'] = $clerk['topic'];
                $response['payload'] = $data;
                $data = \fxapp\Param::json($response, 'encode');
                break;
        }
        $packet['clerk'] = $clerk;
        $packet['data'] = base64_encode($data);
        $packet = \fxapp\Param::json($packet, 'encode');
        $packet = $this->dataPack($packet, 'encode', $config['main_transfer']['data_pack']);
        if (true !== $packet[1]) return;
        return $packet[2];
    }

    /**
     * 处理数据-转发
     * @param string $data 数据
     * @return mixed
     */
    public function dataForward($data)
    {
        global $server, $config;
        // 数据检查
        if (!is_string($data)) return;
        // 发送信息
        if (!is_null($server)) {
            $data = str_split($data, 65507);
            foreach ($data as $value) {
                $server->sendto($config['main_transfer']['peer_ip'], $config['main_transfer']['sock_port'], $value);
            }
        } else {
            $this->udpSend($config['main_transfer']['peer_ip'], $config['main_transfer']['sock_port'], $data, 1);
        }
    }

    /**
     * 检查日志-文件
     * @return mixed
     */
    public function logFile()
    {
        global $config;
        $log_file = $config['log_file'];
        foreach ($log_file as $key => $value) {
            $value = $config['log_dir'] . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . date('Ym') . DIRECTORY_SEPARATOR . date('Ymd') . '.txt';
            if (!is_file($value)) {
                // 创建日志目录
                if (!is_dir(dirname($value))) {
                    \fxyin\Dir::create(dirname($value));
                }
                // 生成日志文件
                fopen($value, "w");
            }
            $log_file[$key] = $value;
        }
        $config['log_file'] = $log_file;
        \fxapp\Base::config('elven.common.log_file', $log_file);
        \fxapp\Base::config('elven.manage.log_file', $log_file);
        return $config['log_file'];
    }
}
